/*	
 * Copyright (C) 2010  Mark Schyns
 * 
 * This file is part of Poker League Leaderboard.
 * 
 * Poker League Leaderboard is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Poker League Leaderboard is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Poker League Leaderboard.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.gambit727.pokerleagueleaderboard;

import java.io.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class PLL {

	/**
	 * @param args
	 */

	// Fields.
	private static String directoryPath;
	private static List<Tournament> tournaments = new ArrayList<Tournament>();
	private static Tournament tournament;
	private static List<Player> players = new ArrayList<Player>();
	private static Player player;
	private static String playerId = "";
	private static FSM state = new FSM();

	// Methods.
	public static void main(String[] args) {
		Log.log("Welcome to Poker League Leaderboard!");
		Log.log("Copyright (C) 2010 Mark Schyns under GNU GPL v.3");
		Log.log();

		// Select Directory.
		selectDirectory(args[0]);
		// Parse Files in Directory.
		parseFilesInDirectory();
		// Calculate Player Totals.
		calculatePlayerTotals();
		// Output Leaderboard Report.
		outputLeaderboardReport();
		Log.log("Poker League Leaderboard has completed.");
	}

	private static void selectDirectory(String directoryArg) {
		Log.log("Selecting directory...");
		directoryPath = directoryArg;
		Log.log("...directory set to " + directoryPath);
	}

	private static void parseFilesInDirectory() {
		Log.log("Parsing files in directory...");
		//TODO Add filter to only parse .txt files. 
		// Get all files in directory.
		File dir = new File(directoryPath);
		File[] files = dir.listFiles();

		// Work on a particular file.
		for (int i = 0; i < files.length; i++) {
			if (files[i].isFile()) {
				Log.log("...file: " + files[i].getName());
				// Parse File to Classes.
				parseFile(files[i]);
			}
			// Next File.
		}
	}

	private static void parseFile(File file) {
		Log.log("...parsing file " + file.getName());

		try {
			// Open the file.
			FileInputStream fstream = new FileInputStream(file);
			// Get the DataInputStream.
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			// Read file line by line.
			while ((line = br.readLine()) != null) {
				// Parse Line.
				parseLine(line);
			}
			// Close the input stream
			in.close();
		} catch (Exception e) {// Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

	private static void parseLine(String line) throws ParseException {
		Log.log("     " + line);

		// Determine type (i.e. condition) of line.
		Condition condition = lineCondition(line);
		// Set state.
		state.transition(condition);
		// Process line based on state.
		processState(line);
		// Next Line.
	}


	private static Condition lineCondition(String line) {
		// Clear this flag when a condition is found to eliminate unnecessary testing.
		boolean keepChecking = true;
		
		// Set a default value to return if none of the tests succeed.
		Condition retVal = Condition.READ_UNKNOWN;
		
		// Test for blank line.
		if (keepChecking && isBlankLine(line)) {
			retVal = Condition.READ_BLANK;
			keepChecking = false;
		}
		// Test for tournament header line.
		if (keepChecking && isTournamentLine(line)) {
			retVal = Condition.READ_TOURNAMENT_ID;
			keepChecking = false;
		}
		// Test for satellite line.
		if (keepChecking && isSatelliteLine(line)) {
			retVal = Condition.READ_SATELLITE;
			keepChecking = false;
		}
		// Test for buy-in line.
		if (keepChecking && isBuyInLine(line)) {
			retVal = Condition.READ_BUY_IN;
			keepChecking = false;
		}
		// Test for field size line.
		if (keepChecking && isFieldSizeLine(line)) {
			retVal = Condition.READ_FIELD_SIZE;
			keepChecking = false;
		}
		// Test for target tournament line.
		if (keepChecking && isTargetLine(line)) {
			retVal = Condition.READ_TARGET;
			keepChecking = false;
		}
		// Test for tickets line.
		if (keepChecking && isTicketsLine(line)) {
			retVal = Condition.READ_TICKETS;
			keepChecking = false;
		}
		// Test for prize pool line.
		if (keepChecking && isPrizePoolLine(line)) {
			retVal = Condition.READ_PRIZE_POOL;
			keepChecking = false;
		}
		// Test for timespan line.
		if (keepChecking && isTimespanLine(line)) {
			retVal = Condition.READ_TIMESPAN;
			keepChecking = false;
		}
		// Test for time start line.
		if (keepChecking && isTimeStartLine(line)) {
			retVal = Condition.READ_TIME_START;
			keepChecking = false;
		}
		// Test for time finish line.
		if (keepChecking && isTimeFinishLine(line)) {
			retVal = Condition.READ_TIME_FINISH;
			keepChecking = false;
		}
		// Test for result line.
		if (keepChecking && isResultLine(line)) {
			retVal = Condition.READ_RESULT;
			keepChecking = false;
		}
		
		return retVal;
	}
	
	private static Boolean isBlankLine(String line) {
		// Blank line determined by trimming whitespace and checking if resulting line length is zero.
		return (line.trim().length() == 0);
	}
	
	private static Boolean isTournamentLine(String line) {
		return (line.indexOf("PokerStars Tournament #") >= 0);		
	}

	private static Boolean isSatelliteLine(String line) {
		return (line.indexOf("Super Satellite") >= 0);		
	}

	private static Boolean isBuyInLine(String line) {
		return (line.indexOf("Buy-In:") >= 0);		
	}

	private static Boolean isFieldSizeLine(String line) {
		// Look for the field size substring at the end of the line string.
		// We check at the end in this case because the search string is generic enough that it might
		// be found elsewhere in the file, a player id for example.
		return (line.endsWith("players"));		
	}
	
	private static Boolean isPrizePoolLine(String line) {
		return (line.indexOf("Total Prize Pool:") >= 0);		
	}

	private static Boolean isTargetLine(String line) {
		return (line.indexOf("Target Tournament #") >= 0);		
	}

	private static Boolean isTicketsLine(String line) {
		return (line.indexOf("tickets to the target tournament") >= 0);		
	}

	private static Boolean isTimespanLine(String line) {
		return (isTimeStartLine(line) && isTimeFinishLine(line));		
	}

	private static Boolean isTimeStartLine(String line) {
		return (line.indexOf("Tournament started") >= 0);		
	}

	private static Boolean isTimeFinishLine(String line) {
		return (line.indexOf("Tournament finished") >= 0);		
	}

	private static Boolean isResultLine(String line) {
		//TODO change this to something more specific, regular expression perhaps?
		return (line.indexOf(":") >= 0);		
	}

	private static void processState(String line) throws ParseException {
		Log.log("...state: " + FSM.getState().toString());

		// Get the current state.
		switch (FSM.getState()) {
		// Check the various state possibilities and process accordingly.
		case BEGIN_FILE:
			// Beginning new file, don't need to do anything here.
			break;
		case END_FILE:
			// End of file, don't need to do anything here.
			break;
		case INVALID:
			// Invalid state reached, what should we do here?
			//TODO add processing for invalid states.
			break;
		case ON_TOURNAMENT_ID:
			// Create a new tournament.
			int tournamentId = extractTournamentId(line);
			String description = extractDescription(line);
			tournament = new Tournament(tournamentId, description);
			break;
		case ON_SATELLITE:
			// Informational only, no processing needed.
			break;
		case ON_BUY_IN:
			// Add buy-in info to tournament.
			tournament.setBuyIn(extractFirstDollarAmount(line));
			break;
		case ON_FIELD_SIZE:
			// Add field size to tournament.
			tournament.setFieldSize(extractFieldSize(line));
			break;
		case ON_PRIZE_POOL:
			// Add prize pool to tournament.
			tournament.setPrizePool(extractFirstDollarAmount(line));
			break;
		case ON_TARGET:
			// Informational only, no processing needed. 
			break;
		case ON_TICKETS:
			// Informational only, no processing needed.
			break;
		case ON_TIMESPAN:
			// Add start time to tournament.
			tournament.setDateTime(extractFirstDateTime(line));
			break;
		case ON_TIME_START:
			// Add start time to tournament.
			tournament.setDateTime(extractFirstDateTime(line));
			break;
		case ON_TIME_FINISH:
			// Informational only, no processing needed.
			break;
		case ON_RESULT:
			// Create new finish object.
			playerId = extractPlayerId(line);
			int place = extractPlace(line);
			double cashWon = extractCashWon(line);
			double pointsEarned = calcPointsEarned(cashWon, tournament.getFieldSize(), place, tournament.getBuyIn());
			
			Finish finish = new Finish(tournament.getTournamnetId(), playerId, place);
			finish.setCashWon(cashWon);
			finish.setPointsEarned(pointsEarned);
			
			//TODO Add cash won and points to Finish before continuing.
			//double points = 0;
			//if tournament.
			//finish.setPointsEarned(CalcFinishPoints(tournament.getTournamnetId(), place));
			
			// Add finish to tournament.
			tournament.addFinish(finish);
			
			// Create new player object.
			player = new Player(playerId);
			// Add finish to player.
			player.addFinish(finish);
			// Add player to players collection, similar players will be consolidated later.
			players.add(player);
			break;
		default:
			break;
		}
	}

	private static int extractTournamentId(String line) throws ParseException {
		int retVal = 0;
		String regEx = "\\d{9}";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find()) {
			retVal = Integer.parseInt(matcher.group().trim());
		    found = true;
		}
		if(!found){
		    throw new ParseException("Tournament id not found in: \"" + line + "\"", 0);
		}
		
		return retVal;
	}

	private static String extractDescription(String line) throws ParseException {
		String retVal = "";
		String regEx = ", .+";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find()) {
			retVal = matcher.group();
		    found = true;
		}
		if(!found){
		    throw new ParseException("Tournament description not found in: \"" + line + "\"", 0);
		}
		
		return retVal;
	}

	private static double extractFirstDollarAmount(String line) throws ParseException {
		double retVal = 0;
		String regEx = "[0-9]+\\.[0-9][0-9]";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find() && !found) {
			retVal = Double.parseDouble(matcher.group().trim());
		    found = true;
		}
		if(!found){
		    throw new ParseException("Dollar amount not found in: \"" + line + "\"", 0);
		}
		
		return retVal;
	}

	private static int extractFieldSize(String line) throws ParseException {
		int retVal = 0;
		String regEx = "\\d+";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find() ) {
			retVal = Integer.parseInt(matcher.group().trim());
		    found = true;
		}
		if(!found){
		    throw new ParseException("Field size not found in: \"" + line + "\"", 0);
		}
		
		return retVal;
	}

	private static Date extractFirstDateTime(String line) throws ParseException {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date retVal = dateFormat.parse("2001/01/01 00:00:00");
		String regEx = "\\d{4}/\\d{2}/\\d{2} \\d?\\d:\\d{2}:\\d{2}";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find() ) {
			retVal = dateFormat.parse(matcher.group());
		    found = true;
		}
		if(!found){
		    throw new ParseException("Date and time not found in: \"" + line + "\"", 0);
		}
		
		return retVal;
	}
	
	private static String extractPlayerId(String line) throws ParseException {
		String retVal = "";
		String regEx = ": .+? \\(";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find() ) {
			retVal = matcher.group().trim();
			retVal = retVal.substring(2, retVal.length()-2);
		    found = true;
		}
		if(!found){
		    throw new ParseException("Player ID not found in: \"" + line + "\"", 0);
		}

		return retVal;
	}
	
	private static int extractPlace(String line) throws ParseException {
		int retVal = 0;
		String regEx = "^  \\d+";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);
		boolean found = false;

		while (matcher.find() ) {
			retVal = Integer.parseInt(matcher.group().trim());
		    found = true;
		}
		if(!found){
		    throw new ParseException("Finish place not found in: \"" + line + "\"", 0);
		}

		return retVal;
	}

	private static double extractCashWon(String line) {
		double retVal = 0;
		String regEx = "\\$\\d+\\.\\d{2}";
		Pattern pattern = Pattern.compile(regEx);
		Matcher matcher = pattern.matcher(line);

		while (matcher.find() ) {
			retVal = Double.parseDouble(matcher.group().trim().substring(1));
		}
		
		return retVal;
	}

/*	The formula used to determine the total points awarded to a player (if cash was earned) for any given tournament is:
		Points = 10 * [sqrt(n)/sqrt(k)] * [1+log(b+0.25)]
		Where:
		n is the number of entrants 
		k is the place of finish (k=1 for the first-place finisher, and so on) 
		b is the buy-in amount in US Dollars (excluding entry fee).
*/	
	private static double calcPointsEarned(double cashWon, int n, int k, double b) {
	
		double points = 0;
		
		if (cashWon > 0) {
			points = (double) (10 * (Math.sqrt(n) / Math.sqrt(k)) * (1 + Math.log10(b + 0.25)));
		}
		
		return points;
	}
	
	private static void calculatePlayerTotals() {
		Log.log("Calculating player totals...");
	}

	private static void outputLeaderboardReport() {
		Log.log("Output leaderboard report...");
	}

}
